package com.gitee.fanx.retty.spring.springmvc;

import com.fasterxml.jackson.databind.ObjectMapper;

import com.gitee.fanx.retty.web.PathUtils;
import com.gitee.fanx.retty.web.Route;
import com.gitee.fanx.retty.web.RouteMapping;
import com.gitee.fanx.retty.spring.MethodRoute;
import com.gitee.fanx.retty.spring.ParamResolver;
import io.netty.handler.codec.http.HttpMethod;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.*;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;

/**
 * @author fanhang
 */
public class SpringMappingMethodCallback implements ReflectionUtils.MethodCallback {
	private final ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
	private final RouteMapping routeMapping;
	private final String parentPath;
	private final Object bean;
	private final ObjectMapper objectMapper;

	public SpringMappingMethodCallback(RouteMapping routeMapping, String parentPath, Object bean,
			ObjectMapper objectMapper) {
		this.routeMapping = routeMapping;
		this.parentPath = parentPath;
		this.bean = bean;
		this.objectMapper = objectMapper;
	}

	@Override
	public void doWith(Method method) throws IllegalArgumentException {
		GetMapping getMapping = method.getAnnotation(GetMapping.class);
		if (getMapping != null) {
			Set<String> paths = PathUtils.getFullPaths(parentPath, getMapping.value(), getMapping.path());
			this.addRoute(method, Collections.singleton(HttpMethod.GET), paths);
		}
		PostMapping postMapping = method.getAnnotation(PostMapping.class);
		if (postMapping != null) {
			Set<String> paths = PathUtils.getFullPaths(parentPath, postMapping.value(), postMapping.path());
			this.addRoute(method, Collections.singleton(HttpMethod.POST), paths);
		}
		PutMapping putMapping = method.getAnnotation(PutMapping.class);
		if (putMapping != null) {
			Set<String> paths = PathUtils.getFullPaths(parentPath, putMapping.value(), putMapping.path());
			this.addRoute(method, Collections.singleton(HttpMethod.PUT), paths);
		}
		DeleteMapping deleteMapping = method.getAnnotation(DeleteMapping.class);
		if (deleteMapping != null) {
			Set<String> paths = PathUtils.getFullPaths(parentPath, deleteMapping.value(), deleteMapping.path());
			this.addRoute(method, Collections.singleton(HttpMethod.DELETE), paths);
		}
		PatchMapping patchMapping = method.getAnnotation(PatchMapping.class);
		if (patchMapping != null) {
			Set<String> paths = PathUtils.getFullPaths(parentPath, patchMapping.value(), patchMapping.path());
			this.addRoute(method, Collections.singleton(HttpMethod.PATCH), paths);
		}
		RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
		if (requestMapping != null) {
			Set<HttpMethod> httpMethods = new HashSet<>();
			if (ArrayUtils.isEmpty(requestMapping.method())) {
				Arrays.stream(RequestMethod.values()).forEach(i -> httpMethods.add(HttpMethod.valueOf(i.name())));
			} else {
				Arrays.stream(requestMapping.method()).forEach(i -> httpMethods.add(HttpMethod.valueOf(i.name())));
			}
			Set<String> paths = PathUtils.getFullPaths(parentPath, requestMapping.value(), requestMapping.path());
			this.addRoute(method, httpMethods, paths);
		}
	}

	private void addRoute(Method method, Set<HttpMethod> httpMethods, Set<String> paths) {
		if (httpMethods.isEmpty() || paths.isEmpty()) {
			return;
		}
		List<ParamResolver> resolvers = getParamResolvers(method);
		Route route = new MethodRoute(bean, method, resolvers);
		for (HttpMethod httpMethod : httpMethods) {
			for (String path : paths) {
				this.routeMapping.add(httpMethod, path, route);
			}
		}
	}

	private List<ParamResolver> getParamResolvers(Method method) {
		Parameter[] parameters = method.getParameters();
		if (ArrayUtils.isEmpty(parameters)) {
			return Collections.emptyList();
		}
		List<ParamResolver> resolvers = new ArrayList<>(parameters.length);
		String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
		for (int i = 0; i < parameters.length; i++) {
			Parameter p = parameters[i];
			RequestParam requestParam = p.getAnnotation(RequestParam.class);
			if (requestParam != null) {
				resolvers.add(ParamResolver.requestParam(requestParam, parameterNames[i], p.getType(), objectMapper));
				continue;
			}
			RequestHeader requestHeader = p.getAnnotation(RequestHeader.class);
			if (requestHeader != null) {
				resolvers.add(ParamResolver.headerParam(requestHeader, parameterNames[i], p.getType(), objectMapper));
				continue;
			}
			RequestBody requestBody = p.getAnnotation(RequestBody.class);
			if (requestBody != null) {
				resolvers.add(ParamResolver.requestBody(requestBody, p.getType(), objectMapper));
				continue;
			}
		}
		return resolvers;
	}

}
